﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace ReversiBackend
{
    // delegate für parameterlose void funktion
    public delegate void Callback();
    // delegate für timerfunktion, die eine "Callback"-Funktion bekommt und (ggf.) verzögert ausführt
    public delegate void TimerFunc(Callback c);

    public class ComplexAI : AI
    {

        /// <summary>
        /// Gets or sets the calculation deep for all ComplexAIs.
        /// </summary>
        /// <value>
        /// The deep.
        /// </value>
        public static int Deep { get; set; }
        private static List<Cell> winCells = new List<Cell>();
        // ai calculation may require some time, make sure we do not pause the whole application
        private BackgroundWorker worker;

        public ComplexAI(Game g, int playerIndex, TimerFunc f) : base(g, playerIndex, f)
        {
            // init static stuff once
            if (Deep == 0)
            {
                Deep = 4;
            }
            if (winCells.Count == 0)
            {
                winCells.Add(new Cell(0, 0));
                winCells.Add(new Cell(0, 7));
                winCells.Add(new Cell(7, 0));
                winCells.Add(new Cell(7, 7));
            }

            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
        }

        public override string Name
        {
            get { return "PC (schwierig)"; }
        }


       // Hier werden die rechenaufwendigen Min/Max Funktionen aufgerufen. Dies ist ein einen
       // BackgroundWorker Prozess ausgelagert.
       void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Cell? nextCell;
            if (playerIndex == -1)
            {
                nextCell = Max(Deep, int.MinValue, int.MaxValue, game.Board);
            }
            else
            {
                nextCell = Min(Deep, int.MinValue, int.MaxValue, game.Board);
            }

            e.Result = nextCell;
        }

        // Wenn die Rechnungen fertig sind können sie hier (im Main-Thread) ausgewertet
        // werden (ohne Probleme mit thread-übergreifenden Funktionsaufrufen).
        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Cell? nextCell = e.Result as Cell?;
            if (nextCell != null)
            {
                game.Turn(nextCell.Value);
            }
            else
            {
                game.Pass();
            }
        }


        public override void DoNextMove()
        {
            // TODO: if nextCell == null -> pass
            if (!game.Board.IsGameEnd)
            {
                List<Cell> cells = game.Board.GetValidMoves(playerIndex);
                foreach (Cell c in winCells)
                {
                    if (cells.Contains(c))
                    {
                        game.Turn(c);
                        return;
                    }
                }

                // do the (maybe time-eating) calculations in an own thread to keep the gui alive
                worker.RunWorkerAsync();
            }
        }


        // used as score if a player wins
        public const int MAXINFSCORE = 1000;
        public const int MININFSCORE = -1000;
         
        public static int ScoreBoard(Board b)
        {
            int score = 0;
            //TODO if endspielstand, score = +/- infinity
           // if (b.
            score = b.GetValidMoves(-1).Count() - b.GetValidMoves(1).Count();

            // für jede "Eck-Zelle" mit einem Chip drauf erhöhe score um 5 wenn es ein 
            // Chip von Spieler 1 ist (-> cell value = -1 -> -1*-1*5 = 5), sonst ziehe 5 vom score ab
            // angrenzende felder (x und c) geben entsprechend negative punkte
            foreach(Cell c in winCells){
                score += -1 * 5 * b[c];
                score += 3 * b[new Cell((c.X+1)%8, c.Y)];
                score += 3 * b[new Cell(c.X, (c.Y + 1) % 8)];
                score += 3 * b[new Cell((c.X + 1) % 8, (c.Y + 1) % 8)];
            }

            return score;
        }

        private dynamic Max(int deep, int alpha, int beta, Board board, bool first = true)
        {
            int score = ScoreBoard(board);
            if (deep == 0 || board.IsGameEnd)
            {
                if (score == MAXINFSCORE)
                {
                    score += deep;
                }
                else if (score == MININFSCORE)
                {
                    score -= deep;
                }
                return score;
            }
            Cell? best_cell = null;
            Board newBoard;
            // für jeden möglichen zug von spieler 1
            foreach (Cell cell in board.GetValidMoves(-1))
            {
                // board kopieren
                newBoard = board.Copy();
                // zug simulieren
                newBoard[cell] = -1;
                // score ermitteln (durch Min funktion, die die Aktion von Spieler 2 berechnet)
                int s = Min(deep - 1, alpha, beta, newBoard, false);
                // falls score größer als Beta ist, kann abgebrochen werden
                if (s >= beta)
                {
                    if (first)
                    {
                        return cell;
                    }
                    else
                    {
                        return beta;
                    }
                } if (s > alpha)
                {
                    alpha = s;
                    best_cell = cell;
                }
            }
            if (first)
            {
                return best_cell;
            }
            else { return alpha; }

        }

        private dynamic Min(int deep, int alpha, int beta, Board board, bool first = true)
        {
            int score = ScoreBoard(board);
            if (deep == 0 || board.IsGameEnd)
            {
                if (score == MAXINFSCORE)
                {
                    score += deep;
                }
                else if (score == MININFSCORE)
                {
                    score -= deep;
                }
                return score;
            }
            Cell? best_cell = null;
            Board newBoard;
            // für jeden möglichen zug von spieler 2
            foreach (Cell cell in board.GetValidMoves(1))
            {
                // board kopieren
                newBoard = board.Copy();
                // zug simulieren
                newBoard[cell] = 1;
                // score ermitteln (durch Max funktion, die die Aktion von Spieler 1 berechnet)
                int s = Max(deep - 1, alpha, beta, newBoard, false);
                // falls score größer als Beta ist, kann abgebrochen werden
                if (s <= alpha)
                {
                    if (first)
                    {
                        return cell;
                    }
                    else
                    {
                        return alpha;
                    }
                } if (s < beta)
                {
                    beta = s;
                    best_cell = cell;
                }
            }
            if (first)
            {
                return best_cell;
            }
            else { return beta; }

        }

        public override void Die()
        {

        }
    }
}
